//============================================================================================
/**
 * @file	run_ai.c
 * @brief	マラソンAI
 * @author	Nozomu Saito
 *
 * モジュール名：RunAI_
 */
//============================================================================================

#include "run_ai.h"
#include "run_comm.h"

#define FRONT_CHK_LEN	(160)
#define BACK_CHK_LEN	(80)

#define CELL_MAX	(16)
#define CELL_WIDTH	(40)		//ＡＩセル幅ドット単位
#define CELL_X_OFS	(20)
#define CELL_Y_OFS	(20)

#define LINE_NUM	(4)

#define POKE_NUM	(ENTRY_MEMBER_NUM_MAX)

#define EXE_FRAME_ALONE	(10)			//非通信時AI実行フレーム間隔
#define EXE_FRAME_COMM	(10)			//通信時AI実行フレーム間隔

#define REST_TIME	(90*30)			//ゲーム残り時間90秒

#define NPC_CHG_AT_VAL	(20)		//ＮＰＣが交代をするスタミナ量

#ifdef PM_DEBUG

///#define DBG_AI		//同じところを指定

#endif

typedef struct RUN_CELL_INFO_tag
{
	int CX;
	int CY;
	int X;
	int Y;
	s16 Grading;
	u16 Index;
}CELL_INFO;

typedef  struct INFO_NODE_tag INFO_NODE;

typedef struct INFO_NODE_tag
{
	INFO_NODE *Prev;
	INFO_NODE *Next;
	CELL_INFO *Info;
}INFO_NODE;

typedef struct RUN_POKE_INFO_tag
{
	RUN_POKE *RunPoke;
	int PokeIndex;
	int X;
	int Y;
}AI_POKE_INFO;

typedef struct OBST_INFO_tag
{
	BOOL Valid;
	int X;
	int Y;
	int Type;
	int Height;
}AI_OBST_INFO;

typedef struct POKE_POS_tag
{
	int X;
	int Y;
	int Line;
}POKE_POS;

typedef struct RUN_AI_CONT_tag
{
	AI_POKE_INFO PokeInfo[POKE_NUM];				//ポケモン情報
	AI_OBST_INFO ObstInfo[OBST_NUM_MAX];	//障害物情報
	u8 PokeLine[POKE_NUM];
	CELL_INFO Cell[CELL_MAX];
	BOOL Execute;
	u8 NpcNum;	//0〜3
	u8 ExeNpcIdx;	//0〜2
	u8 Frame;
	u8 CntFrame;
	u8 NpcStartIdx;	//1〜3
	u8 PlayerNum;	//1〜4
	u8 dummy[2];
}RUN_AI_CONT;

static const DefaultCellGrading[CELL_MAX] = {
	2,4,6,6,
	2,4,6,6,
	2,4,6,6,
	2,4,6,6,
};

static void FixXPos(int *ioX, const int inRange);
static CELL_INFO *GetCell(RUN_AI_CONT_PTR ptr);
static u8 SortInfo(CELL_INFO **inInfo, CELL_INFO **outInfo);
static void ExecuteAI(PKTHLN_PTR work, RUN_AI_CONT_PTR ptr, const u8  inTarget, const int inTime);

//----------------------------------------------------------------------------
/**
 *	@brief	初期化
 *
 *	@param	inHeapID		ヒープID
 *	@param	inNPCNum		NPCの数
 *	@param	inType			ゲームタイプ（通信してるか）
 *
 *	@return ptr				コントロールポインタ
 */
//-----------------------------------------------------------------------------
RUN_AI_CONT_PTR RunAI_Init(const int inHeapID, const int inNpcNum, const PKTHLN_GAME_TYPE inType)
{
	RUN_AI_CONT_PTR cnt_ptr;
	//メモリアロケーション
	int size = sizeof(RUN_AI_CONT);
	cnt_ptr = sys_AllocMemory(inHeapID, size);
	MI_CpuClear8( cnt_ptr, size);

	cnt_ptr->NpcNum = inNpcNum;
	cnt_ptr->PlayerNum = ENTRY_MEMBER_NUM_MAX-inNpcNum;
	cnt_ptr->NpcStartIdx = cnt_ptr->PlayerNum;
	if (inType == PKTHLN_GAME_ALONE){
		cnt_ptr->CntFrame = EXE_FRAME_ALONE;
	}else{
		cnt_ptr->CntFrame = EXE_FRAME_COMM;
	}
	cnt_ptr->Frame = cnt_ptr->CntFrame;	//すぐに起動するようにしておく
	return cnt_ptr;
}


//----------------------------------------------------------------------------
/**
 *	@brief	ポケモンデータのセットアップ
 *
 *	@param	ptr				コントロールポインタ
 *	@param	inIdx			プレーヤーインデックス
 *	@param	*inRunPoke		ポケモンデータへのポインタ
 *
 *	@return none
 */
//-----------------------------------------------------------------------------
void RunAI_SetupPokeInfo(	RUN_AI_CONT_PTR ptr,
							const u8 inIdx,
							RUN_POKE *ioRunPoke )
{
	ptr->PokeInfo[inIdx].RunPoke = ioRunPoke;
/**
	//プレーヤーの最終目的地は決して書き換えられないように、そもそもセットしないようにしておく
	if ( (inIdx/ENTRY_MAX) >= ptr->NpcStartIdx ){
		ptr->PokeInfo[inIdx].DstPos = dstPos;
	}else{
		ptr->PokeInfo[inIdx].DstPos = NULL;
	}
*/
}

//----------------------------------------------------------------------------
/**
 *	@brief	障害物データのセットアップ
 *
 *	@param	ptr				コントロールポインタ
 *	@param	inObstData			障害物データ先頭アドレス
 *
 *	@return none
 */
//-----------------------------------------------------------------------------
void RunAI_SetUpObstInfo(RUN_AI_CONT_PTR ptr, const int *inObstData)
{
	int i;
	u8 y;
	u16 x;
	for(i=0;i<OBST_NUM_MAX;i++){
		ptr->ObstInfo[i].Type = inObstData[i] & 0xff;
		y = (inObstData[i] >> 8) & 0xff;
		x = (inObstData[i] >> 16) & 0xffff;
		if (ptr->ObstInfo[i].Type != 0){
			ptr->ObstInfo[i].Valid = TRUE;
			if (ptr->ObstInfo[i].Type == OBST_WALL_S){
				ptr->ObstInfo[i].Height = OBST_WALL_S_WIDTH;
			}else if(ptr->ObstInfo[i].Type == OBST_WALL_L){
				ptr->ObstInfo[i].Height = OBST_WALL_L_WIDTH;
			}else{
				ptr->ObstInfo[i].Height = OBST_MAT_WIDTH;
			}
			ptr->ObstInfo[i].X = x - (ptr->ObstInfo[i].Height/2);
			ptr->ObstInfo[i].Y = INNER_OFS + y - (ptr->ObstInfo[i].Height/2);
			OS_Printf("OBST_POS=%d,%d\n",ptr->ObstInfo[i].X,ptr->ObstInfo[i].Y);
		}else{
			ptr->ObstInfo[i].Valid = FALSE;
		}
	}
}

//----------------------------------------------------------------------------
/**
 *	@brief	解放
 *
 *	@param	ptr				コントロールポインタ
 *
 *	@return none
 */
//-----------------------------------------------------------------------------
void RunAI_End(RUN_AI_CONT_PTR ptr)
{
	//メモリ解放
	sys_FreeMemoryEz(ptr);
}


//----------------------------------------------------------------------------
/**
 *	@brief	ＡＩ実行
 *	@param	work		ポケスロンワークポインタ
 *	@param	ptr			管理ポインタ
 *	@param	inTime		残り時間
 *
 *	@return u8		動かしたＮＰＣインデックス
 */
//-----------------------------------------------------------------------------
u8 RunAI_Execute(PKTHLN_PTR work, RUN_AI_CONT_PTR ptr, const int inTime)
{
	u8 exe_idx;
	exe_idx = POKE_NUM;

	if (ptr->PlayerNum == ENTRY_MEMBER_NUM_MAX){
		//AIなし
		return 0;
	}

	ptr->Frame++;

	if (ptr->Frame >= ptr->CntFrame){
		//実行状態にする
		ptr->Execute = TRUE;
		ptr->Frame = 0;
	}

	if (ptr->Execute){	//実行状態
		int exe_poke;
		//実行NPCのポケ決定
		exe_poke = ptr->NpcStartIdx + ptr->ExeNpcIdx;
		GF_ASSERT(exe_poke >= ptr->NpcStartIdx );
		//実行
		ExecuteAI(work, ptr, exe_poke, inTime);
		exe_idx = exe_poke;
		//実行NPCのシフト
		ptr->ExeNpcIdx++;
		if (ptr->ExeNpcIdx >= ptr->NpcNum){	//シフト後一巡したら、
			ptr->ExeNpcIdx = 0;
			//実行フラグを落とす
			ptr->Execute = FALSE;
		}
	}
	return exe_idx;
}

//----------------------------------------------------------------------------
/**
 *	@brief		AI実行
 *
 * 	@param	work			ポケスロンワークポインタ
 *	@param	ptr				コントローラポインタ
 *	@param	inTarget		対象ポケインデックス
 *	@param	inTime			残り時間
 *
 *	@return	none
 */
//-----------------------------------------------------------------------------
static void ExecuteAI(PKTHLN_PTR work, RUN_AI_CONT_PTR ptr, const u8  inTarget, const int inTime)
{
	int chg_grading;
	CELL_INFO *cell;
	int i, j;
	AI_POKE_INFO *target_info;
	AI_POKE_INFO poke_info[POKE_NUM];
	AI_OBST_INFO obst_info[OBST_NUM_MAX];

	int line_point[LINE_NUM];

	target_info = &ptr->PokeInfo[inTarget];

	//交代中、気絶中、ヒットポーズ中は処理をスルー
	{
		RUN_POKE *poke;
		poke = target_info->RunPoke;
		if ( poke->ChgFlg ||
			 (poke->Param[ poke->RunPokeIdx ].FatState == POKE_ST_PANIC) ||
			 poke->HitFrame ){
			OS_Printf("ＡＩ行動不可\n");
			return;
		}
	}

	//ラインポイント初期化
	for(i=0;i<LINE_NUM;i++){
		line_point[i] = 0;
	}
	//交代評価初期化
	chg_grading = 0;

	//すべてのポケ(4体分)の座標をセット。ならびに所属ラインを算出
	for(i=0;i<POKE_NUM;i++){
		RUN_POKE *poke;
		int tsx,tsy;
		fx32 x,y;

		poke_info[i] = ptr->PokeInfo[i];
		poke = poke_info[i].RunPoke;
		PokeAct_GetTouchPosByShadowPos(poke->PokeActPtr[poke->RunPokeIdx],
					poke->Pos.x/FX32_ONE, poke->Pos.y/FX32_ONE, &tsx, &tsy);
		PokeAct_GetHitPosByTouchPos(poke->PokeActPtr[poke->RunPokeIdx],
				tsx*FX32_ONE, tsy*FX32_ONE, &x, &y);

		poke_info[i].X = x / FX32_ONE;
		poke_info[i].Y = y / FX32_ONE;
		ptr->PokeLine[i] = (poke_info[i].Y - INNER_OFS) /CELL_WIDTH; //0〜4になるはず。4はライン外ということになる。
		GF_ASSERT(ptr->PokeLine[i] <=4 );
		OS_Printf("pokeinfoXY = %d,%d\n",poke_info[i].X,poke_info[i].Y);
	}

	//セットしなおし
	target_info = &poke_info[inTarget];
	//すべての障害物の手前座標をセット
	for(i=0;i<OBST_NUM_MAX;i++){
		obst_info[i] = ptr->ObstInfo[i];
	}

	OS_Printf("ポケモンのライン\n");
	for(i=0;i<POKE_NUM;i++){
		OS_Printf("line = %d\n",ptr->PokeLine[i]);
	}

	//自分がゴール160ドット手前以内にいる場合は、すべてのものを160ドット手前にして算出する
	//-160してマイナスになった分を1024に足す
	if (target_info->X >= 1024-FRONT_CHK_LEN){
		for(i=0;i<POKE_NUM;i++){
			FixXPos(&poke_info[i].X, -FRONT_CHK_LEN);
		}
		//すべての障害物の手前座標を算出
		for(i=0;i<OBST_NUM_MAX;i++){
			FixXPos(&obst_info[i].X, BACK_CHK_LEN);
		}
	}
	//自分の現在座標から、１６個のセル中心座標を算出
	for(i=0;i<CELL_MAX;i++){
		ptr->Cell[i].Index = i;
		ptr->Cell[i].CX = target_info->X + CELL_X_OFS + (i%4)*CELL_WIDTH;
		ptr->Cell[i].CY = INNER_OFS + CELL_Y_OFS + (i/4)*CELL_WIDTH;
		//選出先決定
		ptr->Cell[i].X = target_info->X + (CELL_WIDTH*(i%4))+(gf_rand()%CELL_WIDTH);
		ptr->Cell[i].Y = INNER_OFS  + (CELL_WIDTH*(i/4)) + (gf_rand()%CELL_WIDTH);
		OS_Printf("CELLXY = %d,%d\n",ptr->Cell[i].X,ptr->Cell[i].Y);
	}
	//座標補正が終了したら、評価を始める。
	{
		int dis;
		//16セルに基本評点をつける
		for(i=0;i<CELL_MAX;i++){
			ptr->Cell[i].Grading = DefaultCellGrading[i];
		}
#ifndef DBG_AI
		//ライン別にポイント減算
		//自分の所属ラインと同じ所属のポケが、前方160ドット以内にいる場合は、減算ポイントが高い
		for(i=0;i<LINE_NUM;i++){
			//ポケモン判定
			for(j=0;j<POKE_NUM;j++){
				if(j==inTarget){		//自分は判定しない
					continue;
				}
				if (target_info->X + FRONT_CHK_LEN < poke_info[j].X){					//前方範囲外なら判定しない
					continue;
				}

				dis = poke_info[j].X - target_info->X;

				if ( (0<=dis)&&(dis<FRONT_CHK_LEN) ){	//前方
					//ライン判定
					if (ptr->PokeLine[j] == i){
						if (i == ptr->PokeLine[inTarget]){		//今調べてるラインに自分がいるとき
							line_point[i] -= 4;//減算値大きい
						}else{
							line_point[i] -= 2;//通常減算
						}
					}
					//ポケのいるグリッドに対して、直接減算
					{
						u8 x_idx;
						//グリッドを特定
						x_idx = dis/CELL_WIDTH;
						if (x_idx>=4){
							GF_ASSERT(0);
						}else{
							ptr->Cell[i*4+x_idx].Grading -= 3;
						}
					}
				}else if(-BACK_CHK_LEN <= dis){			//後方
					//ライン判定
					if (ptr->PokeLine[j] == i){
						if (i == ptr->PokeLine[inTarget]){		//今調べてるラインに自分がいるとき
							line_point[i] += 2;//加算
						}
					}
				}
			}	//for(j=0;j<POKE_NUM;j++)
			//障害物判定
			for(j=0;j<OBST_NUM_MAX;j++){
				if (!obst_info[j].Valid){		//無効障害物を検出したらそれ以降はないとみなす
					break;
				}
				if (target_info->X + FRONT_CHK_LEN < obst_info[j].X){	//前方範囲外なら判定しない
					continue;
				}
				dis = obst_info[j].X - target_info->X;

				if ( (0<dis)&&(dis<=FRONT_CHK_LEN) ){	//前方
					//判定。障害物はライン上？
					int l_top, l_btm;
					int o_top, o_btm;
					l_top = INNER_OFS + (i*CELL_WIDTH);
					l_btm = l_top + CELL_WIDTH;
					o_top = obst_info[j].Y;
					o_btm = o_top + obst_info[j].Height;
					if ( (l_top<o_btm)&&(o_top<l_btm) ){
						//存在するので種類で分岐
						if(obst_info[j].Type == OBST_MAT){		//砂利
							line_point[j] -= 1;
						}else{		//壁
							line_point[j] -= 6;
						}
					}
				}
			}
		}//end for(i=0;i<LINE_NUM;i++)
#endif
		//ライン評価点をセル情報に反映
		for (i=0;i<CELL_MAX;i++){
			ptr->Cell[i].Grading += line_point[i/4];
		}

		//残り時間で評点計算
		if (inTime < REST_TIME){
			//奥の評点上がる
			for(i=0;i<4;i++){
				ptr->Cell[2+i*4].Grading += 3;
				ptr->Cell[3+i*4].Grading += 3;
			}
		}
		//疲労状態で評点計算
		{
			u8 run_idx;
			run_idx = target_info->RunPoke->RunPokeIdx;
			if (target_info->RunPoke->Param[run_idx].Stamina <= NPC_CHG_AT_VAL){
				for(i=0;i<4;i++){
					ptr->Cell[2+i*4].Grading -= 2;
					ptr->Cell[3+i*4].Grading -= 2;
				}
				chg_grading += 8;
			}
		}
		//次のポケの疲労状態で評点計算
		{
			u8 run_next;
			run_next = (target_info->RunPoke->RunPokeIdx+1)%ENTRY_MAX;
			if (target_info->RunPoke->Param[run_next].FatState == POKE_ST_FATIGUE){
				chg_grading -= 5;
			}
		}
	}
#ifdef PM_DEBUG
	//デバッグ
	for(i=0;i<CELL_MAX;i++){
		OS_Printf("%d_GRADING = %d\n",i,ptr->Cell[i].Grading);
	}
#endif
	//一番評点の高いセルを取得(最高点が同じ場合はランダム)
	cell = GetCell(ptr);
	OS_Printf("cellidx = %d\n",cell->Index);
	OS_Printf("XY = %d,%d\n",cell->X, cell->Y);

	//交代か移動か？
	if (cell->Grading >= chg_grading){	//移動
		//プレーヤーの最終目的地等は決して書き換えられないようにする
		if ( inTarget >= ptr->NpcStartIdx ){
			//開始位置セット
			{
				u8 run_idx;
				int x,y;
				run_idx = target_info->RunPoke->RunPokeIdx;
				PokeAct_GetTouchPosByShadowPos(target_info->RunPoke->PokeActPtr[run_idx],
										target_info->X, target_info->Y, &x, &y);
				target_info->RunPoke->TPos.x = x * FX32_ONE;
				target_info->RunPoke->TPos.y = y * FX32_ONE;
				target_info->RunPoke->TPos.z = 0;

				OS_Printf("現在位置：%d,%d__目的地%d,%d\n",x,y,cell->X,cell->Y);
			}
			//目的座標セット
			target_info->RunPoke->LastTPos.x = cell->X * FX32_ONE;
			target_info->RunPoke->LastTPos.y = cell->Y * FX32_ONE;
			target_info->RunPoke->LastTPos.z = 0;
			//移動ベクトル処理実行
			target_info->RunPoke->MoveVecDcd = TRUE;
		}
	}else{		//交代
		//プレーヤーの最終目的地等は決して書き換えられないようにする
		if ( inTarget >= ptr->NpcStartIdx ){
			target_info->RunPoke->ChgFlg = 1;
			target_info->RunPoke->ChgState = CHG_BALL_UP;
			PTFrame_AddActionPoint(	work, target_info->RunPoke->NetID, target_info->RunPoke->RunPokeIdx,
									ADD_TYPE_CHANGE, 1 );
		}
	}
}

//----------------------------------------------------------------------------
/**
 *	@brief	Ｘ座標補正
 *
 *	@param	ioX			Ｘ座標
 *	@param	inRange		補正範囲
 *
 *	@return	none
 */
//-----------------------------------------------------------------------------
static void FixXPos(int *ioX, const int inRange)
{
	(*ioX)+=inRange;
	if (*ioX >= 1024){
		(*ioX) -= 1024;
	}else if(*ioX < 0){
		*ioX += 1024;
	}
}

//----------------------------------------------------------------------------
/**
 *	@brief	一番評価の高いセルを取得
 *
 *	@param	ptr					コントローラポインタ
 *
 *	@return	CELL_INFO *			セルポインタ
 */
//-----------------------------------------------------------------------------
static CELL_INFO *GetCell(RUN_AI_CONT_PTR ptr)
{
	int i;
	u8 num;
	u8 idx;
	CELL_INFO *sort_work[CELL_MAX];
	CELL_INFO *src_cell[CELL_MAX];

#ifdef DBG_AI
	return &ptr->Cell[7];
#endif
	for(i=0;i<CELL_MAX;i++){
		src_cell[i] = &ptr->Cell[i];
	}
	num = SortInfo(src_cell, sort_work);
	//一番高い評価点のセルの中からランダムでひとつ選ぶ
	idx = gf_rand()%num;

	return sort_work[idx];
}

//----------------------------------------------------------------------------
/**
 *	@brief	データソート
 *
 *	@param	*inInfo		ソート前データ
 *	@param	*outInfo	ソート後データ
 *
 *	@return	count		一番大きい値を持った要素数の個数
 */
//-----------------------------------------------------------------------------
static u8 SortInfo(CELL_INFO **inInfo, CELL_INFO **outInfo)
{
	u8 count;
	u8 i,j;
	INFO_NODE node[1+CELL_MAX];

	//準備
	node[0].Prev = &node[1];
	node[0].Next = &node[1];
	node[0].Info = NULL;

	count = 0;

	for(i=0;i<CELL_MAX;i++){
		INFO_NODE *now;
		INFO_NODE *target;
		//ノード取得
		now = &node[i+1];
		//値セット
		now->Info = inInfo[i];
		now->Next = NULL;
		now->Prev = NULL;

		target = &node[0];
		for(j=0;j<i;j++){
			target = target->Next;
			//挿入
			if (target->Info->Grading < now->Info->Grading){	//値が大きい
				//リストの前に追加
				target->Prev->Next = now;
				now->Prev = target->Prev;
				now->Next = target;
				target->Prev = now;
				break;
			}
		}
		if (j==i){
			//リスト最後に追加
			target->Next = now;
			now->Prev = target;
		}
	}
	//ソート終了したら、バッファにデータを格納
	{
		INFO_NODE *now;
		u8 check;
		int max_val;
		//最大値の取得
		max_val = node[0].Next->Info->Grading;
		now = &node[0];
		for(i=0;i<CELL_MAX;i++){
			now = now->Next;
			outInfo[i] = now->Info;
			//一番高い値がいくつあるかカウント
			if (max_val == outInfo[i]->Grading){
				count++;
			}
		}
	}

	GF_ASSERT(count > 0);

	return count;
}